/**
 * Copyright (C) 2011  Robin Dietrich, Nils Kenneweg, Leonard Stutz
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 **/

#include "chunkManager.hpp"

coordinate operator-(coordinate const& p1, coordinate const& p2) {
  coordinate result;
  result.x = p1.x-p2.x;
  result.y = p1.y-p2.y;
  result.z = p1.z-p2.z;
  
  return result;
}

namespace minecraft {
    chunkManager::chunkManager() {
        this->coquit = false;
		this->currentPos.x = 0;
		this->currentPos.y = 0;
		this->currentPos.z = 0;
		
		this->checkList = 0;
    }

    chunkManager::~chunkManager() {
        this->coquit = true;
        this->chunks_created.clear();
    }
    
	bool chunkManager::chunkLoaded(int x, int y, int z) {
		if (this->coquit) return false;
        std::string id = coordinatesToChunkId(x, y, z);
		
		bool found = true;
		
		p_lock.lock();
        if (this->chunks.find(id) == this->chunks.end()) {
			found = false;
		}
		p_lock.unlock();
		
		if (found) {
			this->chunks[id].accessed = time(NULL);
		}
		
		return found;
	}
	
    boost::shared_ptr<chunk> chunkManager::getChunk(int x, int y, int z) {
        if (this->coquit) return boost::shared_ptr<chunk>();
        std::string id = coordinatesToChunkId(x, y, z);
        
		bool found = true;		
         
		p_lock.lock();
        if (this->chunks.find(id) == this->chunks.end()) {
			//std::cout << "loading chunk " << coordinatesToChunkId(x, y, z) << "\n";
		
			//std::cout << "Loading Chunk in Thread: " << boost::this_thread::get_id() << "\n" << std::flush;
			found = false;
        }
        
		if (found) {
			this->chunks[id].accessed = time(NULL);
			boost::shared_ptr<chunk> c = this->chunks[id].obj;
			p_lock.unlock();
			//return chunk
			return c;
		} else {
			p_lock.unlock();
			
			boost::shared_ptr<chunk> c(new chunk(x, y, z));			
             
			p_lock.lock();
				this->chunks[id].obj = c;
				this->chunks[id].accessed = time(NULL);
				this->chunks_created.push_back(id);
				
				/*if (this->checkList > 200) {
					this->checkList = 0;
					for (cMap::iterator it = this->chunks.begin(); it != this->chunks.end(); ++it) {
						coordinate diff = this->currentPos - chunkIdToCoordinates(it->first);
					
						unsigned int distance = diff.x * diff.x + diff.y * diff.y + diff.z * diff.z;
					
						if (distance > 200) {
							std::cout << "[" << it->first << " - " << this->currentPos.x << ":" << this->currentPos.y << ":" << this->currentPos.z << "]: " << distance << "\n";
							it = this->chunks.erase(it);
						}
					}
				} else {
					this->checkList++;
				}*/
				
				//delete element if loaded chunks > chunksmax
				if (this->chunks.size() > P_CHUNKSMAX) {
					time_t max = 0;
					
					cMap::iterator toDelete = this->chunks.begin();
					
					for (cMap::iterator it = this->chunks.begin(); it != this->chunks.end(); ++it) {
						if ((time(NULL) - it->second.accessed) > max) {
							max = (time(NULL) - it->second.accessed);
							toDelete = it;
						}
					}
					
					if (toDelete != this->chunks.end()) {
						//std::cout << "Removing Chunk from Memory\n";
						this->chunks.erase(toDelete);
					}
					
					/*
					this->chunks.erase(this->chunks_created[0]);
					this->chunks_created.erase(this->chunks_created.begin());
					*/
				}
			p_lock.unlock();
			
			return c;
		}
    }
    
	localCoordinates chunkManager::global2LocalCoordinates(int x,int y, int z) {
		localCoordinates data;
		
		if (x >= 0 ) {
			data.chunk.x = (int) (x / P_CHUNKSIZE);
		} else {
			data.chunk.x = (int)  ((x + 1 - P_CHUNKSIZE) / P_CHUNKSIZE);
		}

		if (y >= 0 ) {
			data.chunk.y = (int) (y / P_CHUNKSIZE);
		} else {
			data.chunk.y = (int)  ((y + 1 - P_CHUNKSIZE) / P_CHUNKSIZE);
		}
		
		if (z >= 0 ) {
			data.chunk.z = (int) (z / P_CHUNKSIZE);
		} else {
			data.chunk.z = (int)  ((z + 1 - P_CHUNKSIZE) / P_CHUNKSIZE);
		}
		
		data.local.x = x-(P_CHUNKSIZE*data.chunk.x);
		data.local.y = y-(P_CHUNKSIZE*data.chunk.y);
		data.local.z = z-(P_CHUNKSIZE*data.chunk.z);
		
		return data;
	}
	
	void chunkManager::setCurrentPosition(coordinate theCurrentPos) {
		this->currentPos = theCurrentPos;
	}
	
    b_type chunkManager::getBlock(int x, int y, int z) {
		localCoordinates coordinates = global2LocalCoordinates(x,y,z);

		if (!this->chunkLoaded(coordinates.chunk.x, coordinates.chunk.y, coordinates.chunk.z)) {
			std::cout << "Warning: Chunk not loaded for getBlock!" << "\n";
		}
		
        boost::shared_ptr<chunk> c = this->getChunk(coordinates.chunk.x, coordinates.chunk.y, coordinates.chunk.z);
		
        return c->get(coordinates.local.x, coordinates.local.y, coordinates.local.z);   
    }
    
    void chunkManager::setBlock(int x, int y, int z, b_type val) {
		localCoordinates coordinates = this->global2LocalCoordinates(x,y,z);

        boost::shared_ptr<chunk> c = this->getChunk(coordinates.chunk.x, coordinates.chunk.y, coordinates.chunk.z);
        
        //now set the corresponding block
        c->update(coordinates.local.x, coordinates.local.y, coordinates.local.z, val);
    }
}

